
{Parser} = require 'jison'

unwrap = /^function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/

o = (patternString, action, options) ->
  patternString = patternString.replace /\s{2,}/g, ' '
  return [patternString, '$$ = $1;', options] unless action
  action = if match = unwrap.exec action then match[1] else "(#{action}())"
  action = action.replace /\bnew /g, '$&yy.'
  [patternString, "$$ = #{action};", options]

grammar = 

  Root: [
    o 'Query EOF'
  ]
  
  Query: [
    o "SelectExpression"
    o "InsertQuery"
    o "DeleteQuery"
    o "UpdateQuery"
    o "CreateTableQuery"
    o "DropTableQuery"
  ]
  
  SelectExpression : [
    o "SelectQuery"
    o "SelectQuery Unions",                                -> $1.unions = $2; $1  
  ]
  
  SelectQuery: [
    o "SelectWithLimitQuery"
    o "BasicSelectQuery"
  ]
  
  BasicSelectQuery: [
    o 'Select'
    o 'Select OrderClause',                               -> $1.order = $2; $1
    o 'Select GroupClause',                               -> $1.group = $2; $1
    o 'Select GroupClause OrderClause',                   -> $1.group = $2; $1.order = $3; $1
  ]
  
  SelectWithLimitQuery: [
    o 'SelectQuery LimitClause',                          -> $1.limit = $2; $1
  ]
  
  Select: [
    o 'SelectClause'
    o 'SelectClause WhereClause',                         -> $1.where = $2; $1
  ]
  
  SelectClause: [
    o 'SELECT Fields FROM Table',                         -> new Select($2, $4, false)
    o 'SELECT DISTINCT Fields FROM Table',                -> new Select($3, $5, true)
    o 'SELECT Fields FROM Table Joins',                   -> new Select($2, $4, false, $5)
    o 'SELECT DISTINCT Fields FROM Table Joins',          -> new Select($3, $5, true, $6)
  ]
  
  InsertQuery: [
    o "INSERT INTO Literal VALUES LEFT_PAREN ArgumentList RIGHT_PAREN",   -> new Insert($3, $6)
    o "INSERT INTO Literal SelectQuery",   -> new Insert($3, $4)
    o "INSERT INTO Literal LEFT_PAREN Fields RIGHT_PAREN VALUES LEFT_PAREN ArgumentList RIGHT_PAREN",   -> new Insert($3, $9, $5)
    o "INSERT INTO Literal LEFT_PAREN Fields RIGHT_PAREN SelectQuery",   -> new Insert($3, $7, $5)
  ]

  
  DeleteQuery: [
    o "DELETE STAR FROM Literal",   -> new Delete($4)
    o "DELETE STAR FROM Literal WhereClause",   -> new Delete($4, $5)
    o "DELETE FROM Literal",   -> new Delete($3)
    o "DELETE FROM Literal WhereClause",   -> new Delete($3, $4)
  ]

  UpdateQuery: [
    o "UPDATE Literal SET AssignmentList",   -> new Update($2, $4)
    o "UPDATE Literal SET AssignmentList WhereClause",   -> new Update($2, $4, $5)
  ]
  
  CreateTableQuery : [
    o 'CREATE TABLE Literal LEFT_PAREN FieldDefList RIGHT_PAREN',    -> new CreateTable($3, $5)
    o 'CREATE TABLE DIRECTIVES Literal LEFT_PAREN FieldDefList RIGHT_PAREN',    -> new CreateTable($4, $6, true)
  ]

  DropTableQuery : [
    o 'DROP TABLE Literal',    -> new DropTable($3, false)
    #o 'DROP TABLE IF EXISTS Literal',    -> new DropTable($5, true)
    o 'DROP TABLE DIRECTIVES Literal',    -> new DropTable($4, true)
  ]

  Table: [
    o 'Literal',                                          -> new Table($1)
    o 'Literal AS Literal',                                          -> new Table($1, $3)
    o 'Literal Literal',                                          -> new Table($1, $2)
    o 'LEFT_PAREN SelectExpression RIGHT_PAREN',                     -> new SubSelect($2)
    o 'LEFT_PAREN SelectExpression RIGHT_PAREN Literal',             -> new SubSelect($2, $4)
    o 'LEFT_PAREN SelectExpression RIGHT_PAREN AS Literal',             -> new SubSelect($2, $5)
    o 'Literal WINDOW WINDOW_FUNCTION LEFT_PAREN Number RIGHT_PAREN',
                                                          -> new Table($1, $2, $3, $5)
  ]

  SubQuery: [
    o 'LEFT_PAREN SelectExpression RIGHT_PAREN',                     -> new SubQuery($2)
  ]
  
  Unions: [
    o 'Union',                                            -> [$1]
    o 'Unions Union',                                     -> $1.concat($3)
  ]
  
  Union: [
    o 'UNION SelectQuery',                                -> new Union($2)
    o 'UNION ALL SelectQuery',                            -> new Union($3, true)
  ]
  
  Joins: [
    o 'Join',                                             -> [$1]
    o 'Joins Join',                                       -> $1.concat($2)
  ]
  
  Join: [
    o 'JOIN Table ON Expression',                         -> new Join($2, $4)
    o 'LEFT JOIN Table ON Expression',                    -> new Join($3, $5, 'LEFT', 'OUTER')
    o 'RIGHT JOIN Table ON Expression',                   -> new Join($3, $5, 'RIGHT', 'OUTER')
    o 'LEFT INNER JOIN Table ON Expression',              -> new Join($4, $6, 'LEFT', 'INNER')
    o 'INNER JOIN Table ON Expression',              -> new Join($3, $5, 'LEFT', 'INNER')
    o 'RIGHT INNER JOIN Table ON Expression',             -> new Join($4, $6, 'RIGHT', 'INNER')
    o 'LEFT OUTER JOIN Table ON Expression',              -> new Join($4, $6, 'LEFT', 'OUTER')
    o 'RIGHT OUTER JOIN Table ON Expression',             -> new Join($4, $6, 'RIGHT', 'OUTER')
    o 'SEPARATOR Table',                                             -> new Join($2, null, 'LEFT', 'INNER')
  ]
  
  WhereClause: [
    o 'WHERE Expression',                                 -> new Where($2)
  ]
  
  LimitClause: [
    o 'LIMIT Number',                                     -> new Limit(null, $2)
    o 'LIMIT Number SEPARATOR Number',                         -> new Limit($2, $4)
  ]
  
  OrderClause: [
    o 'ORDER BY OrderArgs',                               -> new Order($3)
  ]
  
  OrderArgs: [
    o 'OrderArg',                                         -> [$1]
    o 'OrderArgs SEPARATOR OrderArg',                     -> $1.concat($3)
  ]
  
  OrderArg: [
    o 'Expression',                                             -> new OrderArgument($1, 'ASC')
    o 'Expression DIRECTION',                             -> new OrderArgument($1, $2) 
    o 'Expression COLLATE NOCASE',                    -> new OrderArgument($1, 'ASC', 'NOCASE')
    o 'Expression COLLATE NOCASE DIRECTION',   -> new OrderArgument($1, $4, 'NOCASE')
  ]
  
  GroupClause: [
    o 'GroupBasicClause'
    o 'GroupBasicClause HavingClause',                    -> $1.having = $2; $1
  ]
  
  GroupBasicClause: [
    o 'GROUP BY ArgumentList',                            -> new Group($3)
  ]
  
  HavingClause: [
    o 'HAVING Expression',                                -> new Having($2)
  ]
  
  
  Expression: [
    o 'SubQuery'
    o 'LEFT_PAREN Expression RIGHT_PAREN',                -> $2
    o 'Expression MATH Expression',                       -> new Op($2, $1, $3)
    o 'Expression MATH_MULTI Expression',                 -> new Op($2, $1, $3)
    o 'Expression OPERATOR Expression',                   -> new Op($2, $1, $3)
    o 'Expression CONDITIONAL Expression',                -> new Op($2, $1, $3)
    o 'Literal IN LEFT_PAREN ArgumentList RIGHT_PAREN', -> new Op('IN', $1, $4)
    o 'Literal IN SubQuery', -> new Op('IN', $1, $3)
    o 'Value'
  ]
  
  Value: [
    o 'Literal'
    o 'Number'
    o 'Boolean'
    o 'String'
    o 'Function'
    o 'UserFunction'
  ]

  Literal: [
    o 'LITERAL',                                          -> new LiteralValue($1)
    o 'LEFT_BRACKET LITERAL RIGHT_BRACKET', -> new LiteralValue($2)	
    o 'Literal DOT LITERAL',                              -> new LiteralValue($1, $3)
    o 'FUNCTION',                                          -> new LiteralValue($1)
  ]
  
  Number: [
    o 'NUMBER',                                           -> new NumberValue($1)
    o 'MATH NUMBER',                                           -> new NumberValue($2, $1)
  ]
  
  Boolean: [
    o 'BOOLEAN',                                           -> new BooleanValue($1)
  ]
  
  String: [
    o 'STRING',                                           -> new StringValue($1, "'")
    o 'DBLSTRING',                                        -> new StringValue($1, '"')
    #o 'DBLSTRING DBLSTRING DBLSTRING',                                        -> new StringValue('"' + $2 + '"', '"')
  ]
  
  Function: [
    o "FUNCTION LEFT_PAREN Expression AS Literal RIGHT_PAREN",     -> new FunctionCastValue($1, $3, $5)  
    o "FUNCTION LEFT_PAREN STAR RIGHT_PAREN",     -> new FunctionValue($1, [$3])
    o "FUNCTION LEFT_PAREN ArgumentList RIGHT_PAREN",     -> new FunctionValue($1, $3)
  ]

  UserFunction: [
    o "LITERAL LEFT_PAREN ArgumentList RIGHT_PAREN",     -> new FunctionValue($1, $3, true)
  ]
  
  ArgumentList: [
    o 'Expression',                                       -> [$1]
    o 'ArgumentList SEPARATOR Expression',                     -> $1.concat($3)
  ]
  
  AssignmentList : [
    o 'Expression',  ->[$1]
    o 'AssignmentList SEPARATOR Expression',  -> $1.concat($3)
  ]
  
  Fields: [
    o 'Field',                                            -> [$1]
    o 'Fields SEPARATOR Field',                           -> $1.concat($3)
  ]
  
  Field: [
    o 'STAR',                                             -> new Star('')
    o 'Literal DOT STAR',                             -> new Star($1)
    o 'Expression',                                       -> new Field($1, $1)
    o 'Expression AS Literal',                            -> new Field($1, $3)
    o 'Expression AS String',                            -> new Field($1, $3)
  ]
  
  FieldDefList: [
    o 'FieldDef',  -> [$1]
    o 'FieldDefList SEPARATOR FieldDef', -> $1.concat($3)
  ]
  
  FieldDef: [
    o 'Literal', -> new FieldDef($1, 'text', true, false, false)
    o 'Literal Literal', -> new FieldDef($1, $2, true, false, false)
    o 'Literal Literal FIELD_DEF_NOT_NULL', -> new FieldDef($1, $2, false, false, false)
    o 'Literal Literal FIELD_DEF_NOT_NULL PRIMARY KEY', -> new FieldDef($1, $2, false, true, false)
    o 'Literal Literal FIELD_DEF_NOT_NULL PRIMARY KEY AUTOINCREMENT', -> new FieldDef($1, $2, false, true, true)
    o 'Literal Literal PRIMARY KEY', -> new FieldDef($1, $2, true, true, false)
    o 'Literal Literal PRIMARY KEY AUTOINCREMENT', -> new FieldDef($1, $2, true, true, true)	
  ]

tokens = []
operators = [
  ['left', 'Op']
  ['left', 'MATH_MULTI']
  ['left', 'MATH']
  ['left', 'OPERATOR']
  ['left', 'CONDITIONAL']
]

for name, alternatives of grammar
  grammar[name] = for alt in alternatives
    for token in alt[0].split ' '
      tokens.push token unless grammar[token]
    alt[1] = "return #{alt[1]}" if name is 'Root'
    alt

exports.parser = new Parser
  tokens      : tokens.join ' '
  bnf         : grammar
  operators   : operators.reverse()
  startSymbol : 'Root'
